package sketch.ast;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;

import sketch.util.Checker;
import sketch.util.Globals;

public class ASTFormatter {
	
	public final static String LEFT_PAREN = "{";
	
	public final static String RIGHT_PAREN = "}";
	
	public static String formatMethodsAndRemoveParenth(String methodStr) {
		MethodDeclaration[] methods = ASTUtils.parseMethods(methodStr);
		StringBuilder sb = new StringBuilder();
		
		for(MethodDeclaration method : methods) {
			String methodAfterFormat = format(method);
			String[] methodStrings = methodAfterFormat.split(Globals.lineSep);
			sb.append(removeParentheses(methodStrings));
		}
		
		return formatMethods(sb.toString());
	}
	
	private static String removeParentheses(String[] methodStrings) {
		StringBuilder sb = new StringBuilder();
		
		List<String> lines = new LinkedList<String>();
		Set<Integer> removeIndex = new HashSet<Integer>();
		
		for(int i = 0; i < methodStrings.length; i++) {
			String line = methodStrings[i];
			//System.out.println(line);
			lines.add(line);
			//sb.append(line);
			//sb.append(Globals.lineSep);
			if(line.trim().equals(RIGHT_PAREN) && lines.get(i - 1).trim().equals(LEFT_PAREN)) {
				removeIndex.add(i);
				removeIndex.add(i - 1);
			}
			else if(line.trim().equals(RIGHT_PAREN) /*&& lines.get(i-1).trim().equals(RIGHT_PAREN)*/) {
				int matched_left_index = -1;
				int right_p = 0;
				for(int matchedIndex = i - 1; matchedIndex >= 0; matchedIndex--) {
					String l = lines.get(matchedIndex);
					if(l.trim().endsWith(RIGHT_PAREN)) {
						right_p++;
					}
					if(l.trim().endsWith(LEFT_PAREN)) {
						right_p--;
					}
					if(right_p == -1 && l.trim().equals(LEFT_PAREN)) {
						matched_left_index = matchedIndex;
						break;
					} else if (right_p == -1) {
						break;
					}
				}
				if(matched_left_index != -1) {
					removeIndex.add(i);
					removeIndex.add(matched_left_index);
				}
			}
		}
		
		for(int i = 0; i < lines.size(); i++) {
			if(removeIndex.contains(i)) {
				continue;
			} else {
				sb.append(lines.get(i));
				sb.append(Globals.lineSep);
			}
		}
		
		return sb.toString();
	}
	
	public static String formatMethods(String methodStr) {
		MethodDeclaration[] methods = ASTUtils.parseMethods(methodStr);
		StringBuilder sb = new StringBuilder();
		
		for(MethodDeclaration method : methods) {
			sb.append(format(method));
			sb.append(Globals.lineSep);
		}
		
		return sb.toString();
	}
	
	public static String formatUnit(String unitStr) {
		return format(ASTUtils.parseUnit(unitStr));
	}
	
	public static String format(CompilationUnit unit) {
		return format(unit, CodeFormatter.K_COMPILATION_UNIT);
	}
	
	public static String format(MethodDeclaration method) {
		return format(method, CodeFormatter.K_CLASS_BODY_DECLARATIONS);
	}
	
	private static<T extends ASTNode> String format(T node, int node_type) {
		Checker.checkNull(node, "The ast node to format could not be null.");
		// take default Eclipse formatting options
		Map options = DefaultCodeFormatterConstants.getEclipseDefaultSettings();
		
		// initialize the compiler settings to be able to format 1.5 code
		options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_5);
		options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_5);
		options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5);
		
		// change the option to wrap each enum constant on a new line
		options.put(
			DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ENUM_CONSTANTS,
			DefaultCodeFormatterConstants.createAlignmentValue(
				true,
				DefaultCodeFormatterConstants.WRAP_ONE_PER_LINE,
				DefaultCodeFormatterConstants.INDENT_ON_COLUMN));
		
		// instantiate the default code formatter with the given options
		final CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(options);
		
		// retrieve the source to format
		String source = null;
		source = node.toString();
		final TextEdit edit = codeFormatter.format(
			node_type,//CodeFormatter.K_COMPILATION_UNIT, // format a compilation unit
			source, // source to format
			0, // starting position
			source.length(), // length
			0, // initial indentation
			System.getProperty("line.separator") // line separator
		);
		
		IDocument document = new Document(source);
		try {
			edit.apply(document);
		} catch (MalformedTreeException e) {
			e.printStackTrace();
		} catch (BadLocationException e) {
			e.printStackTrace();
		}
		
		// display the formatted string on the System out
		return document.get();
	}
	
}